// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/renderer_host/input/touch_event_queue.h"

#include <utility>

#include "base/auto_reset.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/renderer_host/input/timeout_monitor.h"
#include "content/common/input/web_touch_event_traits.h"
#include "ui/events/base_event_utils.h"
#include "ui/gfx/geometry/point_f.h"

using blink::WebInputEvent;
using blink::WebTouchEvent;
using blink::WebTouchPoint;
using ui::LatencyInfo;

namespace content {
namespace {

    // Time interval at which touchmove events will be forwarded to the client while
    // scrolling is active and possible.
    const double kAsyncTouchMoveIntervalSec = .2;

    // A sanity check on touches received to ensure that touch movement outside
    // the platform slop region will cause scrolling.
    const double kMaxConceivablePlatformSlopRegionLengthDipsSquared = 60. * 60.;

    TouchEventWithLatencyInfo ObtainCancelEventForTouchEvent(
        const TouchEventWithLatencyInfo& event_to_cancel)
    {
        TouchEventWithLatencyInfo event = event_to_cancel;
        WebTouchEventTraits::ResetTypeAndTouchStates(
            WebInputEvent::TouchCancel,
            // TODO(rbyers): Shouldn't we use a fresh timestamp?
            event.event.timeStampSeconds(), &event.event);
        return event;
    }

    bool ShouldTouchTriggerTimeout(const WebTouchEvent& event)
    {
        return (event.type() == WebInputEvent::TouchStart || event.type() == WebInputEvent::TouchMove) && event.dispatchType == WebInputEvent::Blocking;
    }

    // Compare all properties of touch points to determine the state.
    bool HasPointChanged(const WebTouchPoint& point_1,
        const WebTouchPoint& point_2)
    {
        DCHECK_EQ(point_1.id, point_2.id);
        if (point_1.screenPosition != point_2.screenPosition || point_1.position != point_2.position || point_1.radiusX != point_2.radiusX || point_1.radiusY != point_2.radiusY || point_1.rotationAngle != point_2.rotationAngle || point_1.force != point_2.force || point_1.tiltX != point_2.tiltX || point_1.tiltY != point_2.tiltY) {
            return true;
        }
        return false;
    }

} // namespace

// Cancels a touch sequence if a touchstart or touchmove ack response is
// sufficiently delayed.
class TouchEventQueue::TouchTimeoutHandler {
public:
    TouchTimeoutHandler(TouchEventQueue* touch_queue,
        base::TimeDelta desktop_timeout_delay,
        base::TimeDelta mobile_timeout_delay)
        : touch_queue_(touch_queue)
        , desktop_timeout_delay_(desktop_timeout_delay)
        , mobile_timeout_delay_(mobile_timeout_delay)
        , use_mobile_timeout_(false)
        , pending_ack_state_(PENDING_ACK_NONE)
        , timeout_monitor_(base::Bind(&TouchTimeoutHandler::OnTimeOut,
              base::Unretained(this)))
        , enabled_(true)
        , enabled_for_current_sequence_(false)
        , sequence_awaiting_uma_update_(false)
        , sequence_using_mobile_timeout_(false)
    {
        SetUseMobileTimeout(false);
    }

    ~TouchTimeoutHandler()
    {
        LogSequenceEndForUMAIfNecessary(false);
    }

    void StartIfNecessary(const TouchEventWithLatencyInfo& event)
    {
        if (pending_ack_state_ != PENDING_ACK_NONE)
            return;

        if (!enabled_)
            return;

        const base::TimeDelta timeout_delay = GetTimeoutDelay();
        if (timeout_delay.is_zero())
            return;

        if (!ShouldTouchTriggerTimeout(event.event))
            return;

        if (WebTouchEventTraits::IsTouchSequenceStart(event.event)) {
            LogSequenceStartForUMA();
            enabled_for_current_sequence_ = true;
        }

        if (!enabled_for_current_sequence_)
            return;

        timeout_event_ = event;
        timeout_monitor_.Restart(timeout_delay);
    }

    bool ConfirmTouchEvent(InputEventAckState ack_result)
    {
        switch (pending_ack_state_) {
        case PENDING_ACK_NONE:
            if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
                enabled_for_current_sequence_ = false;
            timeout_monitor_.Stop();
            return false;
        case PENDING_ACK_ORIGINAL_EVENT:
            if (AckedTimeoutEventRequiresCancel(ack_result)) {
                SetPendingAckState(PENDING_ACK_CANCEL_EVENT);
                TouchEventWithLatencyInfo cancel_event = ObtainCancelEventForTouchEvent(timeout_event_);
                touch_queue_->SendTouchEventImmediately(&cancel_event);
            } else {
                SetPendingAckState(PENDING_ACK_NONE);
                touch_queue_->UpdateTouchConsumerStates(timeout_event_.event,
                    ack_result);
            }
            return true;
        case PENDING_ACK_CANCEL_EVENT:
            SetPendingAckState(PENDING_ACK_NONE);
            return true;
        }
        return false;
    }

    bool FilterEvent(const WebTouchEvent& event)
    {
        if (!HasTimeoutEvent())
            return false;

        if (WebTouchEventTraits::IsTouchSequenceStart(event)) {
            // If a new sequence is observed while we're still waiting on the
            // timed-out sequence response, also count the new sequence as timed-out.
            LogSequenceStartForUMA();
            LogSequenceEndForUMAIfNecessary(true);
        }

        return true;
    }

    void SetEnabled(bool enabled)
    {
        if (enabled_ == enabled)
            return;

        enabled_ = enabled;

        if (enabled_)
            return;

        enabled_for_current_sequence_ = false;
        // Only reset the |timeout_handler_| if the timer is running and has not
        // yet timed out. This ensures that an already timed out sequence is
        // properly flushed by the handler.
        if (IsTimeoutTimerRunning()) {
            pending_ack_state_ = PENDING_ACK_NONE;
            timeout_monitor_.Stop();
        }
    }

    void SetUseMobileTimeout(bool use_mobile_timeout)
    {
        use_mobile_timeout_ = use_mobile_timeout;
    }

    bool IsTimeoutTimerRunning() const { return timeout_monitor_.IsRunning(); }

    bool IsEnabled() const
    {
        return enabled_ && !GetTimeoutDelay().is_zero();
    }

private:
    enum PendingAckState {
        PENDING_ACK_NONE,
        PENDING_ACK_ORIGINAL_EVENT,
        PENDING_ACK_CANCEL_EVENT,
    };

    void OnTimeOut()
    {
        LogSequenceEndForUMAIfNecessary(true);
        SetPendingAckState(PENDING_ACK_ORIGINAL_EVENT);
        touch_queue_->FlushQueue();
    }

    // Skip a cancel event if the timed-out event had no consumer and was the
    // initial event in the gesture.
    bool AckedTimeoutEventRequiresCancel(InputEventAckState ack_result) const
    {
        DCHECK(HasTimeoutEvent());
        if (ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
            return true;
        return !WebTouchEventTraits::IsTouchSequenceStart(timeout_event_.event);
    }

    void SetPendingAckState(PendingAckState new_pending_ack_state)
    {
        DCHECK_NE(pending_ack_state_, new_pending_ack_state);
        switch (new_pending_ack_state) {
        case PENDING_ACK_ORIGINAL_EVENT:
            DCHECK_EQ(pending_ack_state_, PENDING_ACK_NONE);
            TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventTimeout", this);
            break;
        case PENDING_ACK_CANCEL_EVENT:
            DCHECK_EQ(pending_ack_state_, PENDING_ACK_ORIGINAL_EVENT);
            DCHECK(!timeout_monitor_.IsRunning());
            DCHECK(touch_queue_->empty());
            TRACE_EVENT_ASYNC_STEP_INTO0(
                "input", "TouchEventTimeout", this, "CancelEvent");
            break;
        case PENDING_ACK_NONE:
            DCHECK(!timeout_monitor_.IsRunning());
            DCHECK(touch_queue_->empty());
            TRACE_EVENT_ASYNC_END0("input", "TouchEventTimeout", this);
            break;
        }
        pending_ack_state_ = new_pending_ack_state;
    }

    void LogSequenceStartForUMA()
    {
        // Always flush any unlogged entries before starting a new one.
        LogSequenceEndForUMAIfNecessary(false);
        sequence_awaiting_uma_update_ = true;
        sequence_using_mobile_timeout_ = use_mobile_timeout_;
    }

    void LogSequenceEndForUMAIfNecessary(bool timed_out)
    {
        if (!sequence_awaiting_uma_update_)
            return;

        sequence_awaiting_uma_update_ = false;

        if (sequence_using_mobile_timeout_) {
            UMA_HISTOGRAM_BOOLEAN("Event.Touch.TimedOutOnMobileSite", timed_out);
        } else {
            UMA_HISTOGRAM_BOOLEAN("Event.Touch.TimedOutOnDesktopSite", timed_out);
        }
    }

    base::TimeDelta GetTimeoutDelay() const
    {
        return use_mobile_timeout_ ? mobile_timeout_delay_ : desktop_timeout_delay_;
    }

    bool HasTimeoutEvent() const
    {
        return pending_ack_state_ != PENDING_ACK_NONE;
    }

    TouchEventQueue* touch_queue_;

    // How long to wait on a touch ack before cancelling the touch sequence.
    const base::TimeDelta desktop_timeout_delay_;
    const base::TimeDelta mobile_timeout_delay_;
    bool use_mobile_timeout_;

    // The touch event source for which we expect the next ack.
    PendingAckState pending_ack_state_;

    // The event for which the ack timeout is triggered.
    TouchEventWithLatencyInfo timeout_event_;

    // Provides timeout-based callback behavior.
    TimeoutMonitor timeout_monitor_;

    bool enabled_;
    bool enabled_for_current_sequence_;

    // Bookkeeping to classify and log whether a touch sequence times out.
    bool sequence_awaiting_uma_update_;
    bool sequence_using_mobile_timeout_;
};

// Provides touchmove slop suppression for a touch sequence until a
// (unprevented) touch will trigger immediate scrolling.
class TouchEventQueue::TouchMoveSlopSuppressor {
public:
    TouchMoveSlopSuppressor()
        : suppressing_touchmoves_(false)
    {
    }

    bool FilterEvent(const WebTouchEvent& event)
    {
        if (WebTouchEventTraits::IsTouchSequenceStart(event)) {
            suppressing_touchmoves_ = true;
            touch_start_location_ = gfx::PointF(event.touches[0].position);
        }

        if (event.type() == WebInputEvent::TouchEnd || event.type() == WebInputEvent::TouchCancel)
            suppressing_touchmoves_ = false;

        if (event.type() != WebInputEvent::TouchMove)
            return false;

        if (suppressing_touchmoves_) {
            if (event.touchesLength > 1) {
                suppressing_touchmoves_ = false;
            } else if (event.movedBeyondSlopRegion) {
                suppressing_touchmoves_ = false;
            } else {
                // No sane slop region should be larger than 60 DIPs.
                DCHECK_LT((gfx::PointF(event.touches[0].position) - touch_start_location_).LengthSquared(),
                    kMaxConceivablePlatformSlopRegionLengthDipsSquared);
            }
        }

        return suppressing_touchmoves_;
    }

    void ConfirmTouchEvent(InputEventAckState ack_result)
    {
        if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
            suppressing_touchmoves_ = false;
    }

    bool suppressing_touchmoves() const { return suppressing_touchmoves_; }

private:
    bool suppressing_touchmoves_;

    // Sanity check that the upstream touch provider is properly reporting whether
    // the touch sequence will cause scrolling.
    gfx::PointF touch_start_location_;

    DISALLOW_COPY_AND_ASSIGN(TouchMoveSlopSuppressor);
};

// This class represents a single coalesced touch event. However, it also keeps
// track of all the original touch-events that were coalesced into a single
// event. The coalesced event is forwarded to the renderer, while the original
// touch-events are sent to the Client (on ACK for the coalesced event) so that
// the Client receives the event with their original timestamp.
class CoalescedWebTouchEvent {
public:
    CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event,
        bool suppress_client_ack)
        : coalesced_event_(event)
        , suppress_client_ack_(suppress_client_ack)
    {
        TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventQueue::QueueEvent", this);
    }

    ~CoalescedWebTouchEvent()
    {
        TRACE_EVENT_ASYNC_END0("input", "TouchEventQueue::QueueEvent", this);
    }

    // Coalesces the event with the existing event if possible. Returns whether
    // the event was coalesced.
    bool CoalesceEventIfPossible(
        const TouchEventWithLatencyInfo& event_with_latency)
    {
        if (suppress_client_ack_)
            return false;

        if (!coalesced_event_.CanCoalesceWith(event_with_latency))
            return false;

        // Addition of the first event to |uncoaleseced_events_to_ack_| is deferred
        // until the first coalesced event, optimizing the (common) case where the
        // event is not coalesced at all.
        if (uncoaleseced_events_to_ack_.empty())
            uncoaleseced_events_to_ack_.push_back(coalesced_event_);

        TRACE_EVENT_INSTANT0(
            "input", "TouchEventQueue::MoveCoalesced", TRACE_EVENT_SCOPE_THREAD);
        coalesced_event_.CoalesceWith(event_with_latency);
        uncoaleseced_events_to_ack_.push_back(event_with_latency);
        DCHECK_GE(uncoaleseced_events_to_ack_.size(), 2U);
        return true;
    }

    void DispatchAckToClient(InputEventAckState ack_result,
        const ui::LatencyInfo* optional_latency_info,
        TouchEventQueueClient* client)
    {
        DCHECK(client);
        if (suppress_client_ack_)
            return;

        if (uncoaleseced_events_to_ack_.empty()) {
            if (optional_latency_info)
                coalesced_event_.latency.AddNewLatencyFrom(*optional_latency_info);
            client->OnTouchEventAck(coalesced_event_, ack_result);
            return;
        }

        DCHECK_GE(uncoaleseced_events_to_ack_.size(), 2U);
        for (WebTouchEventWithLatencyList::iterator
                 iter
             = uncoaleseced_events_to_ack_.begin(),
             end = uncoaleseced_events_to_ack_.end();
             iter != end;
             ++iter) {
            if (optional_latency_info)
                iter->latency.AddNewLatencyFrom(*optional_latency_info);
            client->OnTouchEventAck(*iter, ack_result);
        }
    }

    const TouchEventWithLatencyInfo& coalesced_event() const
    {
        return coalesced_event_;
    }

private:
    // This is the event that is forwarded to the renderer.
    TouchEventWithLatencyInfo coalesced_event_;

    // This is the list of the original events that were coalesced, each requiring
    // future ack dispatch to the client.
    // Note that this will be empty if no coalescing has occurred.
    typedef std::vector<TouchEventWithLatencyInfo> WebTouchEventWithLatencyList;
    WebTouchEventWithLatencyList uncoaleseced_events_to_ack_;

    bool suppress_client_ack_;

    DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent);
};

TouchEventQueue::Config::Config()
    : desktop_touch_ack_timeout_delay(base::TimeDelta::FromMilliseconds(200))
    , mobile_touch_ack_timeout_delay(base::TimeDelta::FromMilliseconds(1000))
    , touch_ack_timeout_supported(false)
{
}

TouchEventQueue::TouchEventQueue(TouchEventQueueClient* client,
    const Config& config)
    : client_(client)
    , dispatching_touch_ack_(false)
    , dispatching_touch_(false)
    , has_handlers_(true)
    , has_handler_for_current_sequence_(false)
    , drop_remaining_touches_in_sequence_(false)
    , touchmove_slop_suppressor_(new TouchMoveSlopSuppressor)
    , send_touch_events_async_(false)
    , last_sent_touch_timestamp_sec_(0)
{
    DCHECK(client);
    if (config.touch_ack_timeout_supported) {
        timeout_handler_.reset(
            new TouchTimeoutHandler(this,
                config.desktop_touch_ack_timeout_delay,
                config.mobile_touch_ack_timeout_delay));
    }
}

TouchEventQueue::~TouchEventQueue()
{
}

void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event)
{
    TRACE_EVENT0("input", "TouchEventQueue::QueueEvent");

    // If the queueing of |event| was triggered by an ack dispatch, defer
    // processing the event until the dispatch has finished.
    if (touch_queue_.empty() && !dispatching_touch_ack_) {
        // Optimization of the case without touch handlers.  Removing this path
        // yields identical results, but this avoids unnecessary allocations.
        PreFilterResult filter_result = FilterBeforeForwarding(event.event);
        if (filter_result != FORWARD_TO_RENDERER) {
            client_->OnFilteringTouchEvent(event.event);
            client_->OnTouchEventAck(event,
                filter_result == ACK_WITH_NO_CONSUMER_EXISTS
                    ? INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
                    : INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
            return;
        }

        // There is no touch event in the queue. Forward it to the renderer
        // immediately.
        touch_queue_.push_back(
            base::MakeUnique<CoalescedWebTouchEvent>(event, false));
        ForwardNextEventToRenderer();
        return;
    }

    // If the last queued touch-event was a touch-move, and the current event is
    // also a touch-move, then the events can be coalesced into a single event.
    if (touch_queue_.size() > 1) {
        CoalescedWebTouchEvent* last_event = touch_queue_.back().get();
        if (last_event->CoalesceEventIfPossible(event))
            return;
    }
    touch_queue_.push_back(
        base::MakeUnique<CoalescedWebTouchEvent>(event, false));
}

void TouchEventQueue::PrependTouchScrollNotification()
{
    TRACE_EVENT0("input", "TouchEventQueue::PrependTouchScrollNotification");

    // The queue should have an in-flight event when this method is called because
    // this method is triggered by InputRouterImpl::SendGestureEvent, which is
    // triggered by TouchEventQueue::AckTouchEventToClient, which has just
    // received an ack for the in-flight event. We leave the head of the queue
    // untouched since it is the in-flight event.
    //
    // However, for the (integration) tests in RenderWidgetHostTest that trigger
    // this method indirectly, they push the TouchScrollStarted event into
    // TouchEventQueue without any way to dispatch it. Below we added a check for
    // non-empty queue to keep those tests as-is w/o exposing internals of this
    // class all the way up.
    if (!touch_queue_.empty()) {
        TouchEventWithLatencyInfo touch(
            WebInputEvent::TouchScrollStarted, WebInputEvent::NoModifiers,
            ui::EventTimeStampToSeconds(ui::EventTimeForNow()), LatencyInfo());
        touch.event.dispatchType = WebInputEvent::EventNonBlocking;

        auto it = touch_queue_.begin();
        DCHECK(it != touch_queue_.end());
        touch_queue_.insert(++it,
            base::MakeUnique<CoalescedWebTouchEvent>(touch, false));
    }
}

void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result,
    const LatencyInfo& latency_info,
    const uint32_t unique_touch_event_id)
{
    TRACE_EVENT0("input", "TouchEventQueue::ProcessTouchAck");

    // We receive an ack for async touchmove from render.
    if (!ack_pending_async_touchmove_ids_.empty() && ack_pending_async_touchmove_ids_.front() == unique_touch_event_id) {
        // Remove the first touchmove from the ack_pending_async_touchmove queue.
        ack_pending_async_touchmove_ids_.pop_front();
        // Send the next pending async touch move once we receive all acks back.
        if (pending_async_touchmove_ && ack_pending_async_touchmove_ids_.empty()) {
            DCHECK(touch_queue_.empty());

            // Dispatch the next pending async touch move when time expires.
            if (pending_async_touchmove_->event.timeStampSeconds() >= last_sent_touch_timestamp_sec_ + kAsyncTouchMoveIntervalSec) {
                FlushPendingAsyncTouchmove();
            }
        }
        return;
    }

    DCHECK(!dispatching_touch_ack_);
    dispatching_touch_ = false;

    if (timeout_handler_ && timeout_handler_->ConfirmTouchEvent(ack_result))
        return;

    touchmove_slop_suppressor_->ConfirmTouchEvent(ack_result);

    if (touch_queue_.empty())
        return;

    // We don't care about the ordering of the acks vs the ordering of the
    // dispatched events because we can receive the ack for event B before the ack
    // for event A even though A was sent before B. This seems to be happening
    // when, for example, A is acked from renderer but B isn't, so the ack for B
    // is synthesized "locally" in InputRouter.
    //
    // TODO(crbug.com/600773): Bring the id checks back when dispatch triggering
    // is sane.

    PopTouchEventToClient(ack_result, latency_info);
    TryForwardNextEventToRenderer();
}

void TouchEventQueue::TryForwardNextEventToRenderer()
{
    DCHECK(!dispatching_touch_ack_);
    // If there are queued touch events, then try to forward them to the renderer
    // immediately, or ACK the events back to the client if appropriate.
    while (!touch_queue_.empty()) {
        const WebTouchEvent& event = touch_queue_.front()->coalesced_event().event;
        PreFilterResult filter_result = FilterBeforeForwarding(event);
        if (filter_result != FORWARD_TO_RENDERER)
            client_->OnFilteringTouchEvent(event);
        switch (filter_result) {
        case ACK_WITH_NO_CONSUMER_EXISTS:
            PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
            break;
        case ACK_WITH_NOT_CONSUMED:
            PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
            break;
        case FORWARD_TO_RENDERER:
            ForwardNextEventToRenderer();
            return;
        }
    }
}

void TouchEventQueue::ForwardNextEventToRenderer()
{
    TRACE_EVENT0("input", "TouchEventQueue::ForwardNextEventToRenderer");

    DCHECK(!empty());
    DCHECK(!dispatching_touch_);
    TouchEventWithLatencyInfo touch = touch_queue_.front()->coalesced_event();

    if (send_touch_events_async_ && touch.event.type() == WebInputEvent::TouchMove) {
        // Throttling touchmove's in a continuous touchmove stream while scrolling
        // reduces the risk of jank. However, it's still important that the web
        // application be sent touches at key points in the gesture stream,
        // e.g., when the application slop region is exceeded or touchmove
        // coalescing fails because of different modifiers.
        bool send_touchmove_now = size() > 1;
        send_touchmove_now |= pending_async_touchmove_ && !pending_async_touchmove_->CanCoalesceWith(touch);
        send_touchmove_now |= ack_pending_async_touchmove_ids_.empty() && (touch.event.timeStampSeconds() >= last_sent_touch_timestamp_sec_ + kAsyncTouchMoveIntervalSec);

        if (!send_touchmove_now) {
            if (!pending_async_touchmove_) {
                pending_async_touchmove_.reset(new TouchEventWithLatencyInfo(touch));
            } else {
                DCHECK(pending_async_touchmove_->CanCoalesceWith(touch));
                pending_async_touchmove_->CoalesceWith(touch);
            }
            DCHECK_EQ(1U, size());
            PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
            // It's possible (though unlikely) that ack'ing the current touch will
            // trigger the queueing of another touch event (e.g., a touchcancel). As
            // forwarding of the queued event will be deferred while the ack is being
            // dispatched (see |OnTouchEvent()|), try forwarding it now.
            TryForwardNextEventToRenderer();
            return;
        }
    }

    last_sent_touch_timestamp_sec_ = touch.event.timeStampSeconds();

    // Flush any pending async touch move. If it can be combined with the current
    // (touchmove) event, great, otherwise send it immediately but separately. Its
    // ack will trigger forwarding of the original |touch| event.
    if (pending_async_touchmove_) {
        if (pending_async_touchmove_->CanCoalesceWith(touch)) {
            pending_async_touchmove_->CoalesceWith(touch);
            pending_async_touchmove_->event.dispatchType = send_touch_events_async_ ? WebInputEvent::EventNonBlocking
                                                                                    : WebInputEvent::Blocking;
            touch = *pending_async_touchmove_;
            pending_async_touchmove_.reset();
        } else {
            FlushPendingAsyncTouchmove();
            return;
        }
    }

    // Note: Touchstart events are marked cancelable to allow transitions between
    // platform scrolling and JS pinching. Touchend events, however, remain
    // uncancelable, mitigating the risk of jank when transitioning to a fling.
    if (send_touch_events_async_ && touch.event.type() != WebInputEvent::TouchStart)
        touch.event.dispatchType = WebInputEvent::EventNonBlocking;

    SendTouchEventImmediately(&touch);
}

void TouchEventQueue::FlushPendingAsyncTouchmove()
{
    DCHECK(!dispatching_touch_);
    std::unique_ptr<TouchEventWithLatencyInfo> touch = std::move(pending_async_touchmove_);
    touch->event.dispatchType = WebInputEvent::EventNonBlocking;
    touch_queue_.push_front(
        base::MakeUnique<CoalescedWebTouchEvent>(*touch, true));
    SendTouchEventImmediately(touch.get());
}

void TouchEventQueue::OnGestureScrollEvent(
    const GestureEventWithLatencyInfo& gesture_event)
{
    if (gesture_event.event.type() == blink::WebInputEvent::GestureScrollBegin) {
        if (has_handler_for_current_sequence_ && !drop_remaining_touches_in_sequence_) {
            DCHECK(!touchmove_slop_suppressor_->suppressing_touchmoves())
                << "A touch handler should be offered a touchmove before scrolling.";
        }

        pending_async_touchmove_.reset();

        return;
    }

    if (gesture_event.event.type() == blink::WebInputEvent::GestureScrollUpdate && gesture_event.event.resendingPluginId == -1) {
        send_touch_events_async_ = true;
    }
}

void TouchEventQueue::OnGestureEventAck(
    const GestureEventWithLatencyInfo& event,
    InputEventAckState ack_result)
{
    // Throttle sending touchmove events as long as the scroll events are handled.
    // Note that there's no guarantee that this ACK is for the most recent
    // gesture event (or even part of the current sequence).  Worst case, the
    // delay in updating the absorption state will result in minor UI glitches.
    // A valid |pending_async_touchmove_| will be flushed when the next event is
    // forwarded. Scroll updates that are being resent from a GuestView are
    // ignored.
    if (event.event.type() == blink::WebInputEvent::GestureScrollUpdate && event.event.resendingPluginId == -1) {
        send_touch_events_async_ = (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED);
    }
}

void TouchEventQueue::OnHasTouchEventHandlers(bool has_handlers)
{
    DCHECK(!dispatching_touch_ack_);
    DCHECK(!dispatching_touch_);
    has_handlers_ = has_handlers;
}

bool TouchEventQueue::IsPendingAckTouchStart() const
{
    DCHECK(!dispatching_touch_ack_);
    if (touch_queue_.empty())
        return false;

    const blink::WebTouchEvent& event = touch_queue_.front()->coalesced_event().event;
    return (event.type() == WebInputEvent::TouchStart);
}

void TouchEventQueue::SetAckTimeoutEnabled(bool enabled)
{
    if (timeout_handler_)
        timeout_handler_->SetEnabled(enabled);
}

void TouchEventQueue::SetIsMobileOptimizedSite(bool mobile_optimized_site)
{
    if (timeout_handler_)
        timeout_handler_->SetUseMobileTimeout(mobile_optimized_site);
}

bool TouchEventQueue::IsAckTimeoutEnabled() const
{
    return timeout_handler_ && timeout_handler_->IsEnabled();
}

bool TouchEventQueue::HasPendingAsyncTouchMoveForTesting() const
{
    return !!pending_async_touchmove_;
}

bool TouchEventQueue::IsTimeoutRunningForTesting() const
{
    return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning();
}

const TouchEventWithLatencyInfo&
TouchEventQueue::GetLatestEventForTesting() const
{
    return touch_queue_.back()->coalesced_event();
}

void TouchEventQueue::FlushQueue()
{
    DCHECK(!dispatching_touch_ack_);
    DCHECK(!dispatching_touch_);
    pending_async_touchmove_.reset();
    drop_remaining_touches_in_sequence_ = true;
    while (!touch_queue_.empty())
        PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
}

void TouchEventQueue::PopTouchEventToClient(InputEventAckState ack_result)
{
    AckTouchEventToClient(ack_result, nullptr);
}

void TouchEventQueue::PopTouchEventToClient(
    InputEventAckState ack_result,
    const LatencyInfo& renderer_latency_info)
{
    AckTouchEventToClient(ack_result, &renderer_latency_info);
}

void TouchEventQueue::AckTouchEventToClient(
    InputEventAckState ack_result,
    const ui::LatencyInfo* optional_latency_info)
{
    DCHECK(!dispatching_touch_ack_);
    if (touch_queue_.empty()) {
        NOTREACHED() << "Too many acks";
        return;
    }
    std::unique_ptr<CoalescedWebTouchEvent> acked_event = std::move(touch_queue_.front());
    DCHECK(acked_event);

    UpdateTouchConsumerStates(acked_event->coalesced_event().event, ack_result);

    // Note that acking the touch-event may result in multiple gestures being sent
    // to the renderer, or touch-events being queued.
    base::AutoReset<bool> dispatching_touch_ack(&dispatching_touch_ack_, true);

    // Skip ack for TouchScrollStarted since it was synthesized within the queue.
    if (acked_event->coalesced_event().event.type() != WebInputEvent::TouchScrollStarted) {
        acked_event->DispatchAckToClient(ack_result, optional_latency_info,
            client_);
    }

    touch_queue_.pop_front();
}

void TouchEventQueue::SendTouchEventImmediately(
    TouchEventWithLatencyInfo* touch)
{
    // TODO(crbug.com/600773): Hack to avoid cyclic reentry to this method.
    if (dispatching_touch_)
        return;

    if (touch->event.type() == WebInputEvent::TouchStart)
        touch->event.touchStartOrFirstTouchMove = true;

    // For touchmove events, compare touch points position from current event
    // to last sent event and update touch points state.
    if (touch->event.type() == WebInputEvent::TouchMove) {
        CHECK(last_sent_touchevent_);
        if (last_sent_touchevent_->type() == WebInputEvent::TouchStart)
            touch->event.touchStartOrFirstTouchMove = true;
        for (unsigned int i = 0; i < last_sent_touchevent_->touchesLength; ++i) {
            const WebTouchPoint& last_touch_point = last_sent_touchevent_->touches[i];
            // Touches with same id may not have same index in Touches array.
            for (unsigned int j = 0; j < touch->event.touchesLength; ++j) {
                const WebTouchPoint& current_touchmove_point = touch->event.touches[j];
                if (current_touchmove_point.id != last_touch_point.id)
                    continue;

                if (!HasPointChanged(last_touch_point, current_touchmove_point))
                    touch->event.touches[j].state = WebTouchPoint::StateStationary;

                break;
            }
        }
    }

    if (touch->event.type() != WebInputEvent::TouchScrollStarted) {
        if (last_sent_touchevent_)
            *last_sent_touchevent_ = touch->event;
        else
            last_sent_touchevent_.reset(new WebTouchEvent(touch->event));
    }

    base::AutoReset<bool> dispatching_touch(&dispatching_touch_, true);

    client_->SendTouchEventImmediately(*touch);

    // A synchronous ack will reset |dispatching_touch_|, in which case the touch
    // timeout should not be started and the count also should not be increased.
    if (dispatching_touch_) {
        if (touch->event.type() == WebInputEvent::TouchMove && touch->event.dispatchType != WebInputEvent::Blocking) {
            // When we send out a uncancelable touch move, we increase the count and
            // we do not process input event ack any more, we will just ack to client
            // and wait for the ack from render. Also we will remove it from the front
            // of the queue.
            ack_pending_async_touchmove_ids_.push_back(
                touch->event.uniqueTouchEventId);
            dispatching_touch_ = false;
            PopTouchEventToClient(INPUT_EVENT_ACK_STATE_IGNORED);
            TryForwardNextEventToRenderer();
            return;
        }

        if (timeout_handler_)
            timeout_handler_->StartIfNecessary(*touch);
    }
}

TouchEventQueue::PreFilterResult
TouchEventQueue::FilterBeforeForwarding(const WebTouchEvent& event)
{
    if (event.type() == WebInputEvent::TouchScrollStarted)
        return FORWARD_TO_RENDERER;

    if (WebTouchEventTraits::IsTouchSequenceStart(event)) {
        has_handler_for_current_sequence_ = false;
        send_touch_events_async_ = false;
        pending_async_touchmove_.reset();
        last_sent_touchevent_.reset();

        touch_sequence_start_position_ = gfx::PointF(event.touches[0].position);
        drop_remaining_touches_in_sequence_ = false;
        if (!has_handlers_) {
            drop_remaining_touches_in_sequence_ = true;
            return ACK_WITH_NO_CONSUMER_EXISTS;
        }
    }

    if (timeout_handler_ && timeout_handler_->FilterEvent(event))
        return ACK_WITH_NO_CONSUMER_EXISTS;

    if (touchmove_slop_suppressor_->FilterEvent(event))
        return ACK_WITH_NOT_CONSUMED;

    if (drop_remaining_touches_in_sequence_ && event.type() != WebInputEvent::TouchCancel) {
        return ACK_WITH_NO_CONSUMER_EXISTS;
    }

    if (event.type() == WebInputEvent::TouchStart) {
        return (has_handlers_ || has_handler_for_current_sequence_)
            ? FORWARD_TO_RENDERER
            : ACK_WITH_NO_CONSUMER_EXISTS;
    }

    if (has_handler_for_current_sequence_) {
        // Only forward a touch if it has a non-stationary pointer that is active
        // in the current touch sequence.
        for (size_t i = 0; i < event.touchesLength; ++i) {
            const WebTouchPoint& point = event.touches[i];
            if (point.state == WebTouchPoint::StateStationary)
                continue;

            // |last_sent_touchevent_| will be non-null as long as there is an
            // active touch sequence being forwarded to the renderer.
            if (!last_sent_touchevent_)
                continue;

            for (size_t j = 0; j < last_sent_touchevent_->touchesLength; ++j) {
                if (point.id != last_sent_touchevent_->touches[j].id)
                    continue;

                if (event.type() != WebInputEvent::TouchMove)
                    return FORWARD_TO_RENDERER;

                // All pointers in TouchMove events may have state as StateMoved,
                // even though none of the pointers have not changed in real.
                // Forward these events when at least one pointer has changed.
                if (HasPointChanged(last_sent_touchevent_->touches[j], point))
                    return FORWARD_TO_RENDERER;

                // This is a TouchMove event for which we have yet to find a
                // non-stationary pointer. Continue checking the next pointers
                // in the |event|.
                break;
            }
        }
    }

    return ACK_WITH_NO_CONSUMER_EXISTS;
}

void TouchEventQueue::UpdateTouchConsumerStates(const WebTouchEvent& event,
    InputEventAckState ack_result)
{
    if (event.type() == WebInputEvent::TouchStart) {
        if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
            send_touch_events_async_ = false;
        has_handler_for_current_sequence_ |= ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS;
    } else if (WebTouchEventTraits::IsTouchSequenceEnd(event)) {
        has_handler_for_current_sequence_ = false;
    }
}

} // namespace content
